/*
 * ServerCommands.cpp
 *
 *  Created on: 15 mei 2013
 *      Author: maarten
 */

#include "ServerStates.hpp"
#include "Config.hpp"
#include "CommandCodes.hpp"
#include "Message.hpp"
#include "MessageParts.hpp"
#include "Logger.hpp"

#include <fstream>
#include <sstream>

#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <algorithm>

#include <sys/stat.h>

ServerStates::ServerStates(ClientConnection aCon) :
		con(aCon) {
	state = STATE_LOGON;
	uploadFile = 0;
	downloadFile = 0;
	uploadSize = 0;
	transferSize = 0;
}

ServerStates::~ServerStates() {
}

void ServerStates::handleClient() {
	Message aMessage;
	bool connected = true;

	while (connected) {
		//check is connected
		aMessage = con.revcMessage();
		if (aMessage.getHeader() < 1) {
			connected = false;
			Logger::getInstance().logImportant("client close connection");
		} else { //switch states
			switch (state) {
			case STATE_LOGON:
				logonState(aMessage);
				break;
			case STATE_IDLE:
				idleState(aMessage);
				break;
			case STATE_UPLOAD:
				uploadState(aMessage);
				break;
			case STATE_DOWNLOAD:
				downloadState(aMessage);
				break;
			default:
				connected = false;
				break;
			}
		}
	}
}

/*
 * STATES
 */

void ServerStates::logonState(Message aMessage) {
	switch (aMessage.getHeader()) {
	case REQUEST_LOGON:
		handleRequestLogon(aMessage);
		break;
	default:
		sendOnlyMessageType(ERROR_UNKOWN_STATE);
		break;
	}
}

void ServerStates::idleState(Message aMessage) {
	switch (aMessage.getHeader()) {
	case REQUEST_OVERVIEW:
		handleRequestOverview(aMessage);
		break;
	case REQUEST_UPLOAD_PREPARE:
		handleRequestUploadPrepare(aMessage);
		break;
	case REQUEST_DOWNLOAD_PREPARE:
		handleRequestDownloadPrepare(aMessage);
		break;
	case REQUEST_DELETE_FILE:
		handleRequestDelete(aMessage);
		break;
	case REQUEST_RENAME_FILE:
		handleRequestRename(aMessage);
		break;
	case REQUEST_ADD_FOLDER:
		handleRequestAddFolder(aMessage);
		break;
	default:
		sendOnlyMessageType(ERROR_UNKOWN_STATE);
		break;
	}
}

void ServerStates::uploadState(Message aMessage) {
	switch (aMessage.getHeader()) {
	case REQUEST_UPLOAD_TRANSFER:
		handleRequestUploadTransfer(aMessage);
		break;
	case REQUEST_UPLOAD_COMPLETE:
		handleRequestUploadComplete(aMessage);
		break;
	default:
		sendOnlyMessageType(ERROR_UNKOWN_STATE);
		break;
	}
}

void ServerStates::downloadState(Message aMessage) {
	switch (aMessage.getHeader()) {
	case REQUEST_DOWNLOAD_TRANSFER:
		handleRequestDownloadTransfer(aMessage);
		break;
	default:
		sendOnlyMessageType(ERROR_UNKOWN_STATE);
		break;
	}
}

void ServerStates::handleRequestLogon(Message aMessage) {
	con.setUsername(aMessage.getBodyNr(LOGON_USERNAME));
	con.setPassword(aMessage.getBodyNr(LOGON_PASSWORD));
	con.checkUserPass();

	if (con.getLogon()) {
		sendResponseLogon();
	} else {
		sendOnlyMessageType(ERROR_LOGIN_FAIL);
	}
}

void ServerStates::sendResponseLogon() {
	Message aMessage;
	std::stringstream buffer;

	//get permitted extensions form config
	std::vector<std::string> extentions = Config::getInstance().getExtentions();

	aMessage.setHeader(RESPONSE_LOGON);

	//make message: set write permitted
	if (con.getWrite()) {
		aMessage.addBody("1");
	} else {
		aMessage.addBody("0");
	}

	//set extensions
	for (std::string temp : extentions) {
		buffer << temp << ",";
	}

	//push extentions
	aMessage.addBody(buffer.str());

	//send message
	con.sendMessage(aMessage);
	state = STATE_IDLE;
}

void ServerStates::handleRequestOverview(Message aMessage) {
	sendResponseOverview(aMessage.getBodyNr(OVERVIEW_PATH));
}

void ServerStates::sendResponseOverview(std::string path) {
	DIR* dir;
	struct dirent *dirEntry;
	std::string systemPath = Config::getInstance().getBasePath();
	systemPath += path;
	dir = opendir(systemPath.c_str());

	std::vector<std::string> dirStructure;
	std::vector<std::string> dirTempListFiles;
	std::vector<std::string> dirTempListDirs;
	std::string dirstring;
	std::string filestring;

	if (dir) {
		while ((dirEntry = readdir(dir)) != NULL) {
			if (dirEntry->d_name[0] != '.') {
				if (dirEntry->d_type == 4) {
					dirTempListDirs.push_back(dirEntry->d_name);
				} else if (dirEntry->d_type == 8) {
					dirTempListFiles.push_back(dirEntry->d_name);
				}
			}
		}
		std::sort(dirTempListDirs.begin(), dirTempListDirs.end());
		std::sort(dirTempListFiles.begin(), dirTempListFiles.end());

		for (int i = 0; i < (signed) dirTempListDirs.size(); i++) {
			dirstring += dirTempListDirs.at(i);
			dirstring += ",";
		}
		for (int i = 0; i < (signed) dirTempListFiles.size(); i++) {
			filestring += dirTempListFiles.at(i);
			filestring += ",";
		}

		dirStructure.push_back(dirstring);
		dirStructure.push_back(filestring);

		Message temp;
		temp.setHeader(RESPONSE_OVERVIEW);
		temp.addBody(dirStructure.at(0));
		temp.addBody(dirStructure.at(1));
		con.sendMessage(temp);
		Logger::getInstance().logImportant("send overview: " + systemPath);
	} else { //can not open dir
		sendOnlyMessageType(ERROR_FILE_FAIL);
		Logger::getInstance().logImportant("fail to get overview");
	}
}

void ServerStates::handleRequestUploadPrepare(Message aMessage) {
	std::stringstream file;

	if (!con.getWrite()) { //check permit write
		sendOnlyMessageType(ERROR_FILE_NOTPERMITTED);
	} else if (uploadFile != 0) { //check file is already open
		sendOnlyMessageType(ERROR_FILE_ISOPEN);
	} else if (atoi(aMessage.getBodyNr(UPLOAD_SIZE).c_str()) > Config::getInstance().getUploadLimit()) {
		sendOnlyMessageType(ERROR_FILE_SIZE);
	} else {
		//check extension
		int punt = aMessage.getBodyNr(UPLOAD_PATH).find(".");
		bool filenameAllowed = false;

		for (std::string extension : Config::getInstance().getExtentions()) {
			if (aMessage.getBodyNr(UPLOAD_PATH).substr(punt + 1).compare(extension) == 0) {
				filenameAllowed = true;
				break;
			}
		}
		if (filenameAllowed) {
			//open file
			file << Config::getInstance().getBasePath();
			file << aMessage.getBodyNr(UPLOAD_PATH);
			uploadFileName = file.str();
			uploadSize = 0;

			uploadFile = open(uploadFileName.c_str(), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH);

			//if file open fail
			if (uploadFile < 0) {
				uploadFile = 0;
				sendOnlyMessageType(ERROR_FILE_FAIL);
			} else {
				//file oke, send response
				sendResponseUploadPrepare();
				std::stringstream temp;
				temp << "Upload: " << uploadFileName << " size: " << atoi(aMessage.getBodyNr(UPLOAD_SIZE).c_str());
				Logger::getInstance().logImportant(temp.str());
			}

		} else {
			sendOnlyMessageType(ERROR_FILE_EXTENTION);
		}
	}
}

void ServerStates::sendResponseUploadPrepare() {
	startTime();
	sendOnlyMessageType(RESPONSE_UPLOAD_PREPARE);
	state = STATE_UPLOAD;
}

void ServerStates::handleRequestUploadTransfer(Message aMessage) {
	int written = 0;
	//check if file is open
	if (uploadFile == 0) {
		sendOnlyMessageType(ERROR_FILE_NOFILE);
	} else {
		written = write(uploadFile, aMessage.getBody(), aMessage.getBodyLength()) != aMessage.getBodyLength();
		transferSize += aMessage.getBodyLength();
		if (written < 0) {
			close(uploadFile);
			uploadFile = 0;
			sendOnlyMessageType(ERROR_FILE_FAIL);
			Logger::getInstance().logImportant("can't write to file");
		} else if ((uploadSize += aMessage.getBodyLength()) > Config::getInstance().getUploadLimit()) {
			sendOnlyMessageType(ERROR_FILE_SIZE);
			Logger::getInstance().logImportant("file size to big");
			close(uploadFile);
		} else {
			sendResponseUploadTransfer();
		}
	}
}

void ServerStates::sendResponseUploadTransfer() {
	sendOnlyMessageType(RESPONSE_UPLOAD_TRANSFER);
}

void ServerStates::handleRequestUploadComplete(Message aMessage) {
	//close file, check for error
	if (uploadFile < 1) {
		sendOnlyMessageType(ERROR_FILE_FAIL);
	} else if (close(uploadFile) < 0) {
		sendOnlyMessageType(ERROR_FILE_FAIL);
	} else {
		uploadFile = 0;
		sendResponseUploadComplete();
	}
}

void ServerStates::sendResponseUploadComplete() {
	sendOnlyMessageType(RESPONSE_UPLOAD_COMPLETE);
	stopTime(transferSize);
	state = STATE_IDLE;
}

void ServerStates::handleRequestDownloadPrepare(Message aMessage) {
	std::stringstream file;

	if (downloadFile != 0) { //check is already opened
		sendOnlyMessageType(ERROR_FILE_ISOPEN);
		std::cout << "ERROR IN STATE MACHINE BUG" << std::endl;
	} else {
		file << Config::getInstance().getBasePath();
		file << aMessage.getBodyNr(DOWNLOAD_PATH);
		//open file
		downloadFileName = file.str();
		downloadFile = open(downloadFileName.c_str(), O_RDONLY);

		//if file open fail
		if (downloadFile < 0) {
			downloadFile = 0;
			Logger::getInstance().logImportant("fail to open file: " + file.str());
			sendOnlyMessageType(ERROR_FILE_FAIL);
		} else {
			//file oke, send response
			sendResponseDownloadPrepare();
			std::stringstream temp;
			temp << "download: " << downloadFileName;
			Logger::getInstance().logImportant(temp.str());
			startTime();
		}
	}
}

void ServerStates::sendResponseDownloadPrepare() {
	sendOnlyMessageType(RESPONSE_DOWNLOAD_PREPARE);
	state = STATE_DOWNLOAD;
}

void ServerStates::handleRequestDownloadTransfer(Message aMessage) {
	//Check if file is open
	if (downloadFile == 0) {
		std::cout << "ERROR IN STATE MACHINE BUG" << std::endl;
		sendOnlyMessageType(ERROR_FILE_NOFILE);
	} else {
		sendResponseDownloadTransfer();
	}
}

void ServerStates::sendResponseDownloadTransfer() {
	Message aMessage;
	char buffer[FILESPLIT];
	bzero(buffer, FILESPLIT);
	int readed;

	//get part of file
	readed = read(downloadFile, buffer, FILESPLIT);
	transferSize += readed;
	if (readed == -1) {
		Logger::getInstance().logImportant("can't read from file");
		sendOnlyMessageType(204);
		state = STATE_IDLE;
	} else if (readed == 0) {
		close(downloadFile);
		downloadFile = 0;
		sendOnlyMessageType(RESPONSE_DOWNLOAD_TRANSFERCOMPLETE);
		stopTime(transferSize);
		state = STATE_IDLE;
	} else {
		//make message
		aMessage.setHeader(RESPONSE_DOWNLOAD_TRANSFER);
		aMessage.addBody(buffer, readed);

		//send message
		con.sendMessage(aMessage);
	}
}

void ServerStates::handleRequestDelete(Message aMessage) {
	std::stringstream file;

	file << Config::getInstance().getBasePath();
	file << aMessage.getBodyNr(REMOVE_PATH);
	int rval = remove(file.str().c_str());

	if (rval != 0) { //check remove fail
		Logger::getInstance().logImportant("fail to remove");
		sendOnlyMessageType(ERROR_FILE_FAIL);
	} else {
		sendResponseDelete();
		Logger::getInstance().logImportant("delete: " + file.str());
	}
}

void ServerStates::sendResponseDelete() {
	sendOnlyMessageType(RESPONSE_DELETE_FILE);
}

void ServerStates::handleRequestRename(Message aMessage) {
	std::stringstream from;
	std::stringstream to;

	from << Config::getInstance().getBasePath();
	from << aMessage.getBodyNr(RENAME_FROM);

	to << Config::getInstance().getBasePath();
	to << aMessage.getBodyNr(RENAME_TO);

	int rval = rename(from.str().c_str(), to.str().c_str());

	if (rval != 0) { //check remove fail
		Logger::getInstance().logImportant("fail to replace");
		sendOnlyMessageType(ERROR_FILE_FAIL);
	} else {
		sendResponseRename();
		Logger::getInstance().logImportant("rename:" + from.str() + " to " + to.str());
	}
}

void ServerStates::sendResponseRename() {
	sendOnlyMessageType(RESPONSE_RENAME_FILE);
}

void ServerStates::handleRequestAddFolder(Message aMessage) {
	std::stringstream map;

	map << Config::getInstance().getBasePath();
	map << aMessage.getBodyNr(MAP_PATH);

	int rval = mkdir(map.str().c_str(), S_IRUSR | S_IWUSR | S_IXUSR);

	if (rval != 0) { //check remove fail
		Logger::getInstance().logImportant("fail to add map");
		sendOnlyMessageType(ERROR_FILE_FAIL);
	} else {
		Logger::getInstance().logImportant("create map: " + map.str());
		sendResponseAddFolder();
	}
}

void ServerStates::sendResponseAddFolder() {
	sendOnlyMessageType(RESPONSE_ADD_FOLDER);
}

void ServerStates::sendOnlyMessageType(int messageType) {
	Message aMessage;
	aMessage.setHeader(messageType);
	con.sendMessage(aMessage);
}

void ServerStates::startTime() {
	gettimeofday(&start, NULL);
	transferSize = 0;
}

void ServerStates::stopTime(int fileSize) {
	gettimeofday(&finish, NULL);
	int msec = finish.tv_sec * 1000 + finish.tv_usec / 1000;
	msec -= start.tv_sec * 1000 + start.tv_usec / 1000;
	double kb = (double) transferSize / (double) 1024;
	double time = (double) msec / (double) 1000;
	double speed = (double) kb / (double) time;
	std::stringstream temp;
	temp << "transferspeed: " << speed << " kB/s time: " << time;
	Logger::getInstance().logImportant(temp.str());

	transferSize = 0;
}
